package edu.rit.drh4818.volume;

import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;

import javax.media.j3d.ImageComponent;
import javax.media.j3d.ImageComponent3D;
import javax.media.j3d.TexCoordGeneration;
import javax.media.j3d.Texture;
import javax.media.j3d.Texture3D;
import javax.vecmath.Vector4f;


/**
 * 
 * A Volume represents a set of data which should be displayed.
 * 
 *  The texture data is created when generateTexture() is called.
 * 
 * @author Darren Headrick
 *
 */
public abstract class Volume implements DirectionConstants 
{
	/**
	 * Texture size.
	 */
	private int texSize;
	private float texGenScale;
	
	/**
	 * Name of volume data.
	 */
	private String name;
	
	/**
	 * The actual volume data.
	 */
    protected short[][][] volData;
    
    /**
	 * Texture information
	 */
	public TexCoordGeneration tg;
	public Texture3D texture;
    
    /**
     * Size of the volume.
     */
	public int dim = 0;
	
	/**
	 * Flag to indicate the volume changes over time.
	 */
	public boolean animated = false;
	
	/**
	 * Coordinate boundaries.
	 */
	public double minCoord, maxCoord;
	
	/**
	 * Colormap for volume.
	 */
	private Colormap color;
	
	/**
	 * Create a new volume.
	 * 
	 * @param dimension Size of volume.
	 * @param name Name of volume.
	 */
	public Volume( int dimension, boolean animated, String name ) 
	{
		dim = dimension;
		this.name = name;		
		this.animated = animated;	
		
		texSize = 16;
		while (texSize < dimension) 
		{
			texSize *= 2;
		}
		
		texGenScale = (float)dim / (float)texSize;
		minCoord = (0.5f) / dim;
		maxCoord = (dim - 0.5f) / dim;
		
		// set up the texture coordinate generation 
		tg = new TexCoordGeneration();
		tg.setFormat(TexCoordGeneration.TEXTURE_COORDINATE_3);
		tg.setPlaneS(new Vector4f(texGenScale, 0.0f, 0.0f, 0.0f));
		tg.setPlaneT(new Vector4f(0.0f, texGenScale, 0.0f, 0.0f));
		tg.setPlaneR(new Vector4f(0.0f, 0.0f, texGenScale, 0.0f));
	}
	
	/**
	 * Set the colormap.
	 * @param cmap
	 */
	public void setColormap( Colormap cmap )
	{
		color = cmap;
	}
	
	/**
	 * Allocate the volume data array.
	 */
    public void setup()
    {
    	volData = new short[dim][dim][dim];
    	generateTexture();
    }
    
    /**
     * Drop the volume data array.
     */
    public void teardown()
    {
    	volData = null;
    	texture = null;
    }
    
    /**
     * Update the data.
     */
	public abstract void update();
	
	/**
	 * Create a new texture model from the data.
	 */
	protected void generateTexture() 
	{
		printMemory();
			
		ColorModel colorModel = ColorModel.getRGBdefault();
		WritableRaster raster = colorModel.createCompatibleWritableRaster(texSize, texSize);
		BufferedImage bImage = new BufferedImage(colorModel, raster, false,	null);

		int[] intData = ((DataBufferInt) raster.getDataBuffer()).getData();
		Texture3D texture = new Texture3D(Texture.BASE_LEVEL, Texture.RGBA, texSize, texSize, texSize);
		ImageComponent3D pArray = 
			new ImageComponent3D(ImageComponent.FORMAT_RGBA, texSize, texSize, texSize );

		for (int i = 0; i < dim; i++) 
		{
			for( int x = 0; x < dim; x++ )
				for( int y = 0; y < dim; y++ )
				{
					int rowData = (texSize - y - 1) * texSize;
					if( volData == null || color == null) 
					{
						intData[ rowData + x ] = 0;
					}
					else
					{
						intData[ rowData + x ] = color.getColorMapping(get(x,y,i) );
					}
				}
			
			pArray.set(i, bImage);
		}

		texture.setImage(0, pArray);
		texture.setEnable(true);
		texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
		texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
		texture.setBoundaryModeS(Texture.CLAMP);
		texture.setBoundaryModeT(Texture.CLAMP);
		texture.setBoundaryModeR(Texture.CLAMP);
		
		this.texture = texture;
		
		printMemory();
	}
    
	/**
	 * Get data point.
	 * @param x X coord
	 * @param y Y coord
	 * @param z Z coord
	 * @return Value at (x,y,z)
	 */
    protected short get( int x, int y, int z )
    {
		if( volData == null ) return 0;
		return volData[x][y][z];
    }
	
    /**
     * Print memory information
     */
    private void printMemory()
    {
    	if( Settings.MEMORY )
    	{
    		Runtime r = Runtime.getRuntime();
    		System.out.println( "Total Memory: " + r.totalMemory() );
    		System.out.println( "Free Memory: " + r.freeMemory() );
    		System.out.println( "Used Memory: " + (r.totalMemory() - r.freeMemory() ) );
    		System.out.println( "==================================" );
    	}
    }
    
	/**
	 * 
	 */
	public String toString()
	{
		return name;
	}
}
